package org.brakepedal.server.controller;

import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import jakarta.websocket.OnClose;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.brakepedal.server.pojo.BtInfo;
import org.brakepedal.server.pojo.BtPairResultVo;
import org.brakepedal.server.service.BtPairingService;
import org.brakepedal.server.support.bluetooth.*;
import org.brakepedal.server.support.exception.BusinessException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * Date: 2024/3/31 22:42
 *
 * @author Huanyu Mark
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class BtOperationController {
    private final BtPairingService pairingService;
    private final ScheduledThreadPoolExecutor scheduler;

    private final SocketIOServer server;
    private final Object scanListReporterLock = new Object();
    private volatile ScheduledFuture<?> scanListReporter;
    private ScheduledFuture<?> testSendTask;

    private BtService pairedBtService;
    @OnConnect
    public void onConnect(SocketIOClient client){
        log.info("connected {}",client);
    }

    @OnClose
    @OnDisconnect
    public void onClose(SocketIOClient client) {
        log.info("closed|disconnect {}",client);
        client.disconnect();
    }

    @OnEvent("/bt/ops/sync")
    public void syncState(SocketIOClient client){
        if(pairedBtService != null) {
            client.joinRoom("PAIR_LISTENERS");
            try {
                client.sendEvent("/bt/ops/paired", new BtInfo(pairedBtService.getPairableDevice()));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        if(scanListReporter != null) {
            client.sendEvent("/bt/ops/started-scan");
            client.joinRoom("SCAN_RESULT_LISTENERS");
        }
    }


    @OnEvent("/bt/ops/pair")
    @SuppressWarnings("unchecked")
    public void pair(SocketIOClient client, Map<String,String> deviceInfo){
        log.info("thread id {}", Thread.currentThread().threadId());
        BtInfo info = new BtInfo();
        BeanMap map = BeanMap.create(info);
        map.putAll(((Map<String, String>) ((Object) deviceInfo.get("base"))));
        client.joinRoom("PAIR_LISTENERS");
        log.info("["+client.getSessionId()+"]pair to {}",info);
        if(info.equals(pairedBtService)) {
            return;
        }
        try {
            var future = pairingService.pair(info);
            future.thenAccept(services->{
                var service = services.stream().findFirst().orElseThrow();
                log.info("connect to service {}",service);
                client.sendEvent("/bt/ops/pair/result",true);
                if(testSend) {
                    if(testSendTask != null) {
                        testSendTask.cancel(true);
                    }
                    testSendTask = scheduler.scheduleAtFixedRate(()->{
                        try {
                            service.getEncoder().open().testSend();
                        } catch (IOException e) {
                            log.error("can not send",e);
                        }
                    }, 1, 2, TimeUnit.SECONDS);
                }
                try {
                    service.getDecoder().open()
                            .registerFrameHandler(BrakePedalStateFrame.class, frame->{
                                log.info("send-frame /bt/data");
                                try {
                                    server.getRoomOperations("PAIR_LISTENERS").sendEvent("/bt/data", frame);
                                } catch (Exception e){
                                    log.error("send-frame /bt/data error",e);
                                }
                            });
                    synchronized (this) {
                        if(pairedBtService != null) {
                            pairedBtService.close();
                        }
                        pairedBtService = service;
                    }
                } catch (IOException e) {
                    client.sendEvent("/bt/data",(Object) null, e.getMessage());
                    log.error("pairing error",e);
                }
            });
            future.exceptionally(e->{
                client.sendEvent("/bt/ops/pair/result",new BtPairResultVo(true, null));
                return null;
            });
        } catch (IOException e) {
            client.sendEvent("/bt/ops/pair/result",new BtPairResultVo(false, e.getMessage()));
            log.error("pairing error",e);
        }
    }

    @OnEvent("/bt/ops/start-scan")
    public void startScan(SocketIOClient client){
        log.info("start-scan {}",client);
        client.joinRoom("SCAN_RESULT_LISTENERS");
        doStartScan(list-> {
            server.getRoomOperations("SCAN_RESULT_LISTENERS")
                    .sendEvent("/bt/ops/scan/result", list);
        });
    }

    @OnEvent("/bt/ops/stop-scan")
    public void stopScan(SocketIOClient client){
        log.info("stop-scan {}",client);
        client.leaveRoom("SCAN_RESULT_LISTENERS");
        if(scanListReporter == null) {
            return;
        }
        synchronized (scanListReporterLock) {
            if(scanListReporter == null) {
                return;
            }
            scanListReporter.cancel(true);
            pairingService.stopScan();
            scanListReporter = null;
        }
    }

    @Value("${bluetooth.test-send}")
    public boolean testSend;

    private void doStartScan(Consumer<Collection<BtInfo>> scanListReporterCb){
        if(scanListReporter != null) {
            return;
        }
        synchronized (scanListReporterLock) {
            if(scanListReporter != null) {
                return;
            }
            scanListReporter = scheduler.scheduleAtFixedRate(()->{
                var btInfos = pairingService.startScan();
//                log.info("scan-list: {}",btInfos);
                scanListReporterCb.accept(btInfos);
            }, 0, 5, TimeUnit.SECONDS);
        }
    }
}
